// ============================================================================
//
// Copyright (C) 2006-2007 Dengues
//
// Google Group: http://groups.google.com/group/dengues
// QQ Group: 24885404
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// ============================================================================
package org.dengues.test;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.StringTokenizer;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.CoolBarManager;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.StatusLineManager;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.operation.ModalContext;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckboxCellEditor;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;

/**
 * Qiang.Zhang.Adolf@gmail.com class global comment. Detailled comment <br/>
 * 
 * @since 2007-9-14 下午11:57:45
 */
public class Librarian extends ApplicationWindow {

    // A static instance to the running application
    private static Librarian APP;

    // Table column names/properties
    public static final String TITLE = "Title"; //$NON-NLS-1$

    public static final String CHECKED_OUT = "?"; //$NON-NLS-1$

    public static final String WHO = "By Whom"; //$NON-NLS-1$

    public static final String[] PROPS = { TITLE, CHECKED_OUT, WHO };

    // The viewer
    private TableViewer viewer;

    // The current library
    private Library library;

    // The actions
    private NewAction newAction;

    private OpenAction openAction;

    private SaveAction saveAction;

    private SaveAsAction saveAsAction;

    private ExitAction exitAction;

    private AddBookAction addBookAction;

    private RemoveBookAction removeBookAction;

    private AboutAction aboutAction;

    private ShowBookCountAction showBookCountAction;

    /**
     * Gets the running application
     */
    public static final Librarian getApp() {
        return APP;
    }

    /**
     * Librarian constructor
     */
    public Librarian() {
        super(null);

        APP = this;

        // Create the data model
        library = new Library();

        // Create the actions
        newAction = new NewAction();
        openAction = new OpenAction();
        saveAction = new SaveAction();
        saveAsAction = new SaveAsAction();
        exitAction = new ExitAction();
        addBookAction = new AddBookAction();
        removeBookAction = new RemoveBookAction();
        aboutAction = new AboutAction();
        showBookCountAction = new ShowBookCountAction();

        addMenuBar();
        addCoolBar(SWT.NONE);
        addStatusLine();
    }

    /**
     * Runs the application
     */
    public void run() {
        // Don't return from open() until window closes
        setBlockOnOpen(true);

        // Open the main window
        open();

        // Dispose the display
        Display.getCurrent().dispose();
    }

    /**
     * Configures the shell
     * 
     * @param shell the shell
     */
    protected void configureShell(Shell shell) {
        super.configureShell(shell);

        // Set the title bar text
        shell.setText("Librarian"); //$NON-NLS-1$
    }

    /**
     * Creates the main window's contents
     * 
     * @param parent the main window
     * @return Control
     */
    protected Control createContents(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        composite.setLayout(new GridLayout(1, false));

        viewer = new TableViewer(composite, SWT.FULL_SELECTION | SWT.BORDER);
        Table table = viewer.getTable();
        table.setLayoutData(new GridData(GridData.FILL_BOTH));

        // Set up the viewer
        viewer.setContentProvider(new LibraryContentProvider());
        viewer.setLabelProvider(new LibraryLabelProvider());
        viewer.setInput(library);
        viewer.setColumnProperties(PROPS);
        viewer.setCellEditors(new CellEditor[] { new TextCellEditor(table), new CheckboxCellEditor(table),
                new TextCellEditor(table) });
        viewer.setCellModifier(new LibraryCellModifier());

        // Set up the table
        for (int i = 0, n = PROPS.length; i < n; i++)
            new TableColumn(table, SWT.LEFT).setText(PROPS[i]);
        table.setHeaderVisible(true);
        table.setLinesVisible(true);

        // Add code to hide or display the book count based on the action
        showBookCountAction.addPropertyChangeListener(new IPropertyChangeListener() {

            public void propertyChange(PropertyChangeEvent event) {
                // The value has changed; refresh the view
                refreshView();
            }
        });

        // Rfresh the view to get the columns right-sized
        refreshView();

        return composite;
    }

    /**
     * Creates the menu for the application
     * 
     * @return MenuManager
     */
    protected MenuManager createMenuManager() {
        // Create the main menu
        MenuManager mm = new MenuManager();

        // Create the File menu
        MenuManager fileMenu = new MenuManager("File"); //$NON-NLS-1$
        mm.add(fileMenu);

        // Add the actions to the File menu
        fileMenu.add(newAction);
        fileMenu.add(openAction);
        fileMenu.add(saveAction);
        fileMenu.add(saveAsAction);
        fileMenu.add(new Separator());
        fileMenu.add(exitAction);

        // Create the Book menu
        MenuManager bookMenu = new MenuManager("Book"); //$NON-NLS-1$
        mm.add(bookMenu);

        // Add the actions to the Book menu
        bookMenu.add(addBookAction);
        bookMenu.add(removeBookAction);

        // Create the View menu
        MenuManager viewMenu = new MenuManager("View"); //$NON-NLS-1$
        mm.add(viewMenu);

        // Add the actions to the View menu
        viewMenu.add(showBookCountAction);

        // Create the Help menu
        MenuManager helpMenu = new MenuManager("Help"); //$NON-NLS-1$
        mm.add(helpMenu);

        // Add the actions to the Help menu
        helpMenu.add(aboutAction);

        return mm;
    }

    /**
     * Creates the toolbar for the application
     */
    protected ToolBarManager createToolBarManager(int style) {
        // Create the toolbar manager
        ToolBarManager tbm = new ToolBarManager(style);

        // Add the file actions
        tbm.add(newAction);
        tbm.add(openAction);
        tbm.add(saveAction);
        tbm.add(saveAsAction);

        // Add a separator
        tbm.add(new Separator());

        // Add the book actions
        tbm.add(addBookAction);
        tbm.add(removeBookAction);

        // Add a separator
        tbm.add(new Separator());

        // Add the show book count, which will appear as a toggle button
        tbm.add(showBookCountAction);

        // Add a separator
        tbm.add(new Separator());

        // Add the about action
        tbm.add(aboutAction);

        return tbm;
    }

    /**
     * Creates the coolbar for the application
     */
    protected CoolBarManager createCoolBarManager(int style) {
        // Create the coolbar manager
        CoolBarManager cbm = new CoolBarManager(style);

        // Add the toolbar
        cbm.add(createToolBarManager(SWT.FLAT));

        return cbm;
    }

    /**
     * Creates the status line manager
     */
    protected StatusLineManager createStatusLineManager() {
        return new StatusLineManager();
    }

    /**
     * Adds a book
     */
    public void addBook() {
        library.add(new Book("[Enter Title]")); //$NON-NLS-1$
        refreshView();
    }

    /**
     * Removes the selected book
     */
    public void removeSelectedBook() {
        Book book = (Book) ((IStructuredSelection) viewer.getSelection()).getFirstElement();
        if (book != null)
            library.remove(book);
        refreshView();
    }

    /**
     * Opens a file
     * 
     * @param fileName the file name
     */
    public void openFile(final String fileName) {
        if (checkOverwrite()) {
            // Disable the actions, so user can't change library while loading
            enableActions(false);

            library = new Library();
            try {
                // Launch the Open runnable
                ModalContext.run(new IRunnableWithProgress() {

                    public void run(IProgressMonitor progressMonitor) {
                        try {
                            progressMonitor.beginTask("Loading", IProgressMonitor.UNKNOWN); //$NON-NLS-1$
                            library.load(fileName);
                            progressMonitor.done();
                            viewer.setInput(library);
                            refreshView();
                        } catch (IOException e) {
                            showError("Can't load file " + fileName + "\r" + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
                        }
                    }
                }, true, getStatusLineManager().getProgressMonitor(), getShell().getDisplay());
            } catch (InterruptedException e) {
            } catch (InvocationTargetException e) {
            } finally {
                // Enable actions
                enableActions(true);
            }
        }
    }

    /**
     * Creates a new file
     */
    public void newFile() {
        if (checkOverwrite()) {
            library = new Library();
            viewer.setInput(library);
        }
    }

    /**
     * Saves the current file
     */
    public void saveFile() {
        String fileName = library.getFileName();
        if (fileName == null) {
            fileName = new SafeSaveDialog(getShell()).open();
        }
        saveFileAs(fileName);
    }

    /**
     * Saves the current file using the specified file name
     * 
     * @param fileName the file name
     */
    public void saveFileAs(final String fileName) {
        // Disable the actions, so user can't change file while it's saving
        enableActions(false);

        try {
            // Launch the Save runnable
            ModalContext.run(new IRunnableWithProgress() {

                public void run(IProgressMonitor progressMonitor) {
                    try {
                        progressMonitor.beginTask("Saving", IProgressMonitor.UNKNOWN); //$NON-NLS-1$
                        library.save(fileName);
                        progressMonitor.done();
                    } catch (IOException e) {
                        showError("Can't save file " + library.getFileName() + "\r" + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
                    }
                }
            }, true, getStatusLineManager().getProgressMonitor(), getShell().getDisplay());
        } catch (InterruptedException e) {
        } catch (InvocationTargetException e) {
        } finally {
            // Enable the actions
            enableActions(true);
        }
    }

    /**
     * Shows an error
     * 
     * @param msg the error
     */
    public void showError(String msg) {
        MessageDialog.openError(getShell(), "Error", msg); //$NON-NLS-1$
    }

    /**
     * Refreshes the view
     */
    public void refreshView() {
        // Refresh the view
        viewer.refresh();

        // Repack the columns
        for (int i = 0, n = viewer.getTable().getColumnCount(); i < n; i++) {
            viewer.getTable().getColumn(i).pack();
        }

        getStatusLineManager().setMessage(
                showBookCountAction.isChecked() ? "Book Count: " + library.getBooks().size() : ""); //$NON-NLS-1$ //$NON-NLS-2$
    }

    /**
     * Checks the current file for unsaved changes. If it has unsaved changes, confirms that user wants to overwrite
     * 
     * @return boolean
     */
    public boolean checkOverwrite() {
        boolean proceed = true;
        if (library.isDirty()) {
            proceed = MessageDialog.openConfirm(getShell(), "Are you sure?", //$NON-NLS-1$
                    "You have unsaved changes--are you sure you want to lose them?"); //$NON-NLS-1$
        }
        return proceed;
    }

    /**
     * Sets the current library dirty
     */
    public void setLibraryDirty() {
        library.setDirty();
    }

    /**
     * Closes the application
     */
    public boolean close() {
        if (checkOverwrite())
            return super.close();
        return false;
    }

    /**
     * Enables or disables the actions
     * 
     * @param enable true to enable, false to disable
     */
    private void enableActions(boolean enable) {
        newAction.setEnabled(enable);
        openAction.setEnabled(enable);
        saveAction.setEnabled(enable);
        saveAsAction.setEnabled(enable);
        exitAction.setEnabled(enable);
        addBookAction.setEnabled(enable);
        removeBookAction.setEnabled(enable);
        aboutAction.setEnabled(enable);
        showBookCountAction.setEnabled(enable);
    }

    /**
     * The application entry point
     * 
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        new Librarian().run();
    }
}

/**
 * This class holds all the books in a library. It also handles loading from and saving to disk
 */

class Library {

    private static final String SEP = "|"; //$NON-NLS-1$

    // The filename
    private String filename;

    // The books
    private Collection books;

    // The dirty flag
    private boolean dirty;

    /**
     * Library constructor Note the signature :-)
     */
    public Library() {
        books = new LinkedList();
    }

    /**
     * Loads the library from a file
     * 
     * @param filename the filename
     * @throws IOException
     */
    public void load(String filename) throws IOException {
        BufferedReader in = new BufferedReader(new LineNumberReader(new FileReader(filename)));
        String line;
        while ((line = in.readLine()) != null) {
            StringTokenizer st = new StringTokenizer(line, SEP);
            Book book = null;
            if (st.hasMoreTokens())
                book = new Book(st.nextToken());
            if (st.hasMoreTokens())
                book.checkOut(st.nextToken());
            if (book != null)
                add(book);
        }
        in.close();
        this.filename = filename;
        dirty = false;
    }

    /**
     * Saves the library to a file
     * 
     * @param filename the filename
     * @throws IOException
     */
    public void save(String filename) throws IOException {
        BufferedWriter out = new BufferedWriter(new FileWriter(filename));
        for (Iterator itr = books.iterator(); itr.hasNext();) {
            Book book = (Book) itr.next();
            out.write(book.getTitle());
            out.write('|');
            out.write(book.getCheckedOutTo() == null ? "" : book.getCheckedOutTo()); //$NON-NLS-1$
            out.write('\r');
        }
        out.close();
        this.filename = filename;
        dirty = false;
    }

    /**
     * Adds a book
     * 
     * @param book the book to add
     * @return boolean
     */
    public boolean add(Book book) {
        boolean added = books.add(book);
        if (added)
            setDirty();
        return added;
    }

    /**
     * Removes a book
     * 
     * @param book the book to remove
     */
    public void remove(Book book) {
        books.remove(book);
        setDirty();
    }

    /**
     * Gets the books
     * 
     * @return Collection
     */
    public Collection getBooks() {
        return Collections.unmodifiableCollection(books);
    }

    /**
     * Gets the file name
     * 
     * @return String
     */
    public String getFileName() {
        return filename;
    }

    /**
     * Gets whether this file is dirty
     * 
     * @return boolean
     */
    public boolean isDirty() {
        return dirty;
    }

    /**
     * Sets this file as dirty
     */
    public void setDirty() {
        dirty = true;
    }
}

/**
 * This action class deletes a book
 */

class RemoveBookAction extends Action {

    /**
     * RemoveBookAction constructor
     */
    public RemoveBookAction() {
        super("&Remove Book@Ctrl+X"); //$NON-NLS-1$
        // setDisabledImageDescriptor(ImageDescriptor.createFromFile(RemoveBookAction.class,
        // "/images/disabledRemoveBook.gif"));
        setToolTipText("Remove"); //$NON-NLS-1$
    }

    /**
     * Removes the selected book after confirming
     */
    public void run() {
        if (MessageDialog.openConfirm(Librarian.getApp().getShell(), "Are you sure?", //$NON-NLS-1$
                "Are you sure you want to remove the selected book?")) { //$NON-NLS-1$
            Librarian.getApp().removeSelectedBook();
        }
    }
}

/**
 * This action class responds to requests open a file
 */

class OpenAction extends Action {

    /**
     * OpenAction constructor
     */
    public OpenAction() {
        super("&Open...@Ctrl+O"); //$NON-NLS-1$
        // setDisabledImageDescriptor(ImageDescriptor.createFromFile(OpenAction.class, "/images/disabledOpen.gif"));
        setToolTipText("Open"); //$NON-NLS-1$
    }

    /**
     * Opens an existing file
     */
    public void run() {
        // Use the file dialog
        FileDialog dlg = new FileDialog(Librarian.getApp().getShell(), SWT.OPEN);
        String fileName = dlg.open();
        if (fileName != null) {
            Librarian.getApp().openFile(fileName);
        }
    }
}

/**
 * This action class adds a book
 */

class AddBookAction extends Action {

    /**
     * AddBookAction constructor
     */
    public AddBookAction() {
        super("&Add Book@Ctrl+B"); //$NON-NLS-1$
        // setDisabledImageDescriptor(ImageDescriptor.createFromFile(AddBookAction.class,
        // "/images/disabledAddBook.gif"));
        setToolTipText("Add"); //$NON-NLS-1$
    }

    /**
     * Adds a book to the current library
     */
    public void run() {
        Librarian.getApp().addBook();
    }
}

/**
 * This action class exits the application
 */

class ExitAction extends Action {

    /**
     * ExitAction constructor
     */
    public ExitAction() {
        super("E&xit@Alt+F4"); //$NON-NLS-1$
        setToolTipText("Exit"); //$NON-NLS-1$
    }

    /**
     * Exits the application
     */
    public void run() {
        Librarian.getApp().close();
    }
}

/**
 * This action class reponds to requests for a new file
 */

class NewAction extends Action {

    /**
     * NewAction constructor
     */
    public NewAction() {
        super("&New@Ctrl+N"); //$NON-NLS-1$
        // setDisabledImageDescriptor(ImageDescriptor.createFromFile(NewAction.class, "/images/disabledNew.gif"));
        setToolTipText("New"); //$NON-NLS-1$
    }

    /**
     * Creates a new file
     */
    public void run() {
        Librarian.getApp().newFile();
    }
}

/**
 * This action class responds to requests to save a file
 */

class SaveAction extends Action {

    /**
     * SaveAction constructor
     */
    public SaveAction() {
        super("&Save@Ctrl+S"); //$NON-NLS-1$
        // setDisabledImageDescriptor(ImageDescriptor.createFromFile(SaveAction.class, "/images/disabledSave.gif"));
        setToolTipText("Save"); //$NON-NLS-1$
    }

    /**
     * Saves the file
     */
    public void run() {
        Librarian.getApp().saveFile();
    }
}

/**
 * This action class responds to requests to save a file as . . .
 */

class SaveAsAction extends Action {

    /**
     * SaveAsAction constructor
     */
    public SaveAsAction() {
        super("Save As..."); //$NON-NLS-1$
        // setDisabledImageDescriptor(ImageDescriptor.createFromFile(SaveAsAction.class, "/images/disabledSaveAs.gif"));
        setToolTipText("Save As"); //$NON-NLS-1$
    }

    /**
     * Saves the file
     */
    public void run() {
        SafeSaveDialog dlg = new SafeSaveDialog(Librarian.getApp().getShell());
        String fileName = dlg.open();
        if (fileName != null) {
            Librarian.getApp().saveFileAs(fileName);
        }
    }
}

/**
 * This action class determines whether to show the book count
 */

class ShowBookCountAction extends Action {

    public ShowBookCountAction() {
        super("&Show Book Count@Ctrl+C", IAction.AS_CHECK_BOX); //$NON-NLS-1$
        setChecked(true);
        // setImageDescriptor(ImageDescriptor.createFromFile(ShowBookCountAction.class, "/images/count.gif"));
        // setDisabledImageDescriptor(ImageDescriptor.createFromFile(ShowBookCountAction.class,
        // "/images/disabledCount.gif"));
    }
}

/**
 * This action class shows an About box
 */

class AboutAction extends Action {

    /**
     * AboutAction constructor
     */
    public AboutAction() {
        super("&About@Ctrl+A"); //$NON-NLS-1$
        // setDisabledImageDescriptor(ImageDescriptor.createFromFile(AboutAction.class, "/images/disabledAbout.gif"));
        setToolTipText("About"); //$NON-NLS-1$
    }

    /**
     * Shows an about box
     */
    public void run() {
        MessageDialog.openInformation(Librarian.getApp().getShell(), "About", "Librarian--to manage your books"); //$NON-NLS-1$ //$NON-NLS-2$
    }
}

/**
 * This class provides a facade for the "save" FileDialog class. If the selected file already exists, the user is asked
 * to confirm before overwriting.
 */

class SafeSaveDialog {

    // The wrapped FileDialog
    private FileDialog dlg;

    /**
     * SafeSaveDialog constructor
     * 
     * @param shell the parent shell
     */
    public SafeSaveDialog(Shell shell) {
        dlg = new FileDialog(shell, SWT.SAVE);
    }

    public String open() {
        // We store the selected file name in fileName
        String fileName = null;

        // The user has finished when one of the
        // following happens:
        // 1) The user dismisses the dialog by pressing Cancel
        // 2) The selected file name does not exist
        // 3) The user agrees to overwrite existing file
        boolean done = false;

        while (!done) {
            // Open the File Dialog
            fileName = dlg.open();
            if (fileName == null) {
                // User has cancelled, so quit and return
                done = true;
            } else {
                // User has selected a file; see if it already exists
                File file = new File(fileName);
                if (file.exists()) {
                    // The file already exists; asks for confirmation
                    MessageBox mb = new MessageBox(dlg.getParent(), SWT.ICON_WARNING | SWT.YES | SWT.NO);

                    // We really should read this string from a
                    // resource bundle
                    mb.setMessage(fileName + " already exists. Do you want to replace it?"); //$NON-NLS-1$

                    // If they click Yes, we're done and we drop out. If
                    // they click No, we redisplay the File Dialog
                    done = mb.open() == SWT.YES;
                } else {
                    // File does not exist, so drop out
                    done = true;
                }
            }
        }
        return fileName;
    }

    public String getFileName() {
        return dlg.getFileName();
    }

    public String[] getFileNames() {
        return dlg.getFileNames();
    }

    public String[] getFilterExtensions() {
        return dlg.getFilterExtensions();
    }

    public String[] getFilterNames() {
        return dlg.getFilterNames();
    }

    public String getFilterPath() {
        return dlg.getFilterPath();
    }

    public void setFileName(String string) {
        dlg.setFileName(string);
    }

    public void setFilterExtensions(String[] extensions) {
        dlg.setFilterExtensions(extensions);
    }

    public void setFilterNames(String[] names) {
        dlg.setFilterNames(names);
    }

    public void setFilterPath(String string) {
        dlg.setFilterPath(string);
    }

    public Shell getParent() {
        return dlg.getParent();
    }

    public int getStyle() {
        return dlg.getStyle();
    }

    public String getText() {
        return dlg.getText();
    }

    public void setText(String string) {
        dlg.setText(string);
    }
}

/**
 * This class represents a book
 */

class Book {

    private String title;

    private String checkedOutTo;

    /**
     * Book constructor
     * 
     * @param title the title
     */
    public Book(String title) {
        setTitle(title);
    }

    /**
     * Sets the title
     * 
     * @param title the title
     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * Gets the title
     * 
     * @return String
     */
    public String getTitle() {
        return title;
    }

    /**
     * Check out
     * 
     * @param who the person checking this book out
     */
    public void checkOut(String who) {
        checkedOutTo = who;
        if (checkedOutTo.length() == 0)
            checkedOutTo = null;
    }

    public boolean isCheckedOut() {
        return checkedOutTo != null && checkedOutTo.length() > 0;
    }

    public void checkIn() {
        checkedOutTo = null;
    }

    /**
     * Gets who this book is checked out to
     * 
     * @return String
     */
    public String getCheckedOutTo() {
        return checkedOutTo;
    }
}

/**
 * This class provides the labels for the library table
 */

class LibraryLabelProvider implements ITableLabelProvider {

    private Image checked;

    private Image unchecked;

    /**
     * LibraryLabelProvider constructor
     */
    public LibraryLabelProvider() {
        // Create the check mark images
        // checked = new Image(null, LibraryLabelProvider.class.getResourceAsStream("/images/checked.gif"));
        // unchecked = new Image(null, LibraryLabelProvider.class.getResourceAsStream("/images/unchecked.gif"));
    }

    /**
     * Gets the column image
     * 
     * @param element the book
     * @param columnIndex the column index
     * @return Image
     */
    public Image getColumnImage(Object element, int columnIndex) {
        // For the "Checked Out" column, return the check mark
        // if the book is checked out
        if (columnIndex == 1)
            return ((Book) element).isCheckedOut() ? checked : unchecked;
        return null;
    }

    /**
     * Gets the column text
     * 
     * @param element the book
     * @param columnIndex the column index
     * @return String
     */
    public String getColumnText(Object element, int columnIndex) {
        Book book = (Book) element;
        String text = null;
        switch (columnIndex) {
        case 0:
            text = book.getTitle();
            break;
        case 2:
            text = book.getCheckedOutTo();
            break;
        }
        return text == null ? "" : text; //$NON-NLS-1$
    }

    /**
     * Adds a listener
     */
    public void addListener(ILabelProviderListener listener) {
        // Ignore
    }

    /**
     * Disposes any resources
     */
    public void dispose() {
        if (checked != null)
            checked.dispose();
        if (unchecked != null)
            unchecked.dispose();
    }

    /**
     * Gets whether this is a label property
     * 
     * @param element the book
     * @param property the property
     * @return boolean
     */
    public boolean isLabelProperty(Object element, String property) {
        return false;
    }

    /**
     * Removes a listener
     * 
     * @param listener the listener
     */
    public void removeListener(ILabelProviderListener listener) {
        // Ignore
    }
}

/**
 * This class provides the content for the library table
 */

class LibraryContentProvider implements IStructuredContentProvider {

    /**
     * Gets the books
     * 
     * @param inputElement the library
     * @return Object[]
     */
    public Object[] getElements(Object inputElement) {
        return ((Library) inputElement).getBooks().toArray();
    }

    /**
     * Disposes any resources
     */
    public void dispose() {
        // Do nothing
    }

    /**
     * Called when the input changes
     * 
     * @param viewer the viewer
     * @param oldInput the old library
     * @param newInput the new library
     */
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        // Ignore
    }
}

/**
 * This class is the cell modifier for the Librarian program
 */

class LibraryCellModifier implements ICellModifier {

    /**
     * Gets whether the specified property can be modified
     * 
     * @param element the book
     * @param property the property
     * @return boolean
     */
    public boolean canModify(Object element, String property) {
        return true;
    }

    /**
     * Gets the value for the property
     * 
     * @param element the book
     * @param property the property
     * @return Object
     */
    public Object getValue(Object element, String property) {
        Book book = (Book) element;
        if (Librarian.TITLE.equals(property))
            return book.getTitle();
        else if (Librarian.CHECKED_OUT.equals(property))
            return Boolean.valueOf(book.isCheckedOut());
        else if (Librarian.WHO.equals(property))
            return book.getCheckedOutTo() == null ? "" : book.getCheckedOutTo(); //$NON-NLS-1$
        else
            return null;
    }

    /**
     * Modifies the element
     * 
     * @param element the book
     * @param property the property
     * @param value the new value
     */
    public void modify(Object element, String property, Object value) {
        if (element instanceof Item)
            element = ((Item) element).getData();

        Book book = (Book) element;
        if (Librarian.TITLE.equals(property))
            book.setTitle((String) value);
        else if (Librarian.CHECKED_OUT.equals(property)) {
            boolean b = ((Boolean) value).booleanValue();
            if (b)
                book.checkOut("[Enter Name]"); //$NON-NLS-1$
            else
                book.checkIn();
        } else if (Librarian.WHO.equals(property))
            book.checkOut((String) value);

        // Refresh the view
        Librarian.getApp().refreshView();

        // Set the library dirty
        Librarian.getApp().setLibraryDirty();
    }
}
